﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MemoryPack;

namespace Hsenl {
    [Serializable]
    [MemoryPackable(GenerateType.NoGenerate)]
    public abstract partial class Node<T> : INode<T> where T : class, IBehaviorTree {
        private string _name;
        private NodeStatus _nowStatus;
        private INode _parent;

        [MemoryPackIgnore]
        protected T manager;

        [MemoryPackInclude]
        public string Name {
            get => this._name;
            set => this._name = value;
        }

        NodeStatus INode.NowStatus {
            get => this._nowStatus;
            // 我们不希望外部可以轻松的设置该值, 只在我们确定可以修改的时候, 再修改它. 同时, 我们又希望, 外部可以通过 INode 实现自己的 node, 并添加到系统的 node 旗下, 一样可以正常运行
            set => throw new NotImplementedException("This function should be called by the system.");
        }

        [MemoryPackIgnore]
        public NodeStatus NowStatus {
            get => this._nowStatus;
            internal set { // nowStatus和parent为了避免外部在不知情下, 随意调用, 造成未知的影响, 所以都写为internal
                if (this._nowStatus == value)
                    return;

                var prevStatus = this._nowStatus;
                this._nowStatus = value;
                if (value == NodeStatus.Running) {
                    if (prevStatus != NodeStatus.Running) {
                        this.StartRun();
                    }
                }
                else {
                    if (prevStatus == NodeStatus.Running) {
                        this.StopRun();
                    }
                }
            }
        }

        INode INode.Parent {
            get => this._parent;
            set => throw new InvalidOperationException("This function should be called by the system.");
        }

        [MemoryPackIgnore]
        public INode Parent {
            get => this._parent;
            internal set => this._parent = value;
        }

        [MemoryPackIgnore]
        public IBlackboard Blackboard => this.manager.Blackboard;

        [MemoryPackIgnore]
        public bool IsAborted => this.manager?.IsAborted ?? true;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        protected void SetNodeStatus(INode node, NodeStatus status) {
            if (node is Node<T> n) {
                n.NowStatus = status;
            }
            else {
                node.NowStatus = status;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static void SetParent_Internal(INode node, INode parent) {
            if (node is Node<T> n)
                n._parent = parent;
            else
                node.Parent = parent;
        }

        public virtual void AwakeNode(IBehaviorTree tree) {
            if (this.manager != null) throw new Exception("already has manager");
            if (tree == null) throw new ArgumentNullException(nameof(tree));
            this.manager = (T)tree;
            try {
                this.OnAwake();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        public virtual void EnableNode() {
            try {
                this.OnEnable();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        public virtual void DisableNode() {
            try {
                this.OnDisable();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        public virtual void DestroyNode() {
            if (this.manager != null) {
                try {
                    this.OnDestroy();
                }
                catch (Exception e) {
                    Log.Error(e);
                }

                this.manager = null;
            }
        }

        public virtual void ResetNode() {
            try {
                this.OnReset();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        public virtual void AbortNode() {
            this.NowStatus = NodeStatus.Failure;
            try {
                this.OnAbort();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        public virtual bool EvaluateNode() {
            try {
                return this.OnNodeEvaluate();
            }
            catch (Exception e) {
                Log.Error(e);
                return false;
            }
        }

        public virtual NodeStatus TickNode() {
            if (this.manager == null)
                return NodeStatus.Return; // node如果在运行过程中, manager被赋空了(destroy可以赋空manager), 那么所有的node都强制返回return, 以阻止继续 tick其他node

            if (this.manager.IsAborted)
                return NodeStatus.Return; // 同上

            try {
                return this.OnNodeTick();
            }
            catch (Exception e) {
                Log.Error(e);
                return NodeStatus.Return;
            }
        }

        public virtual void StartRun() {
            try {
                this.OnNodeRunningStart();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        public virtual void Run() {
            try {
                this.OnNodeRunning();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        public virtual void StopRun() {
            try {
                this.OnNodeRunningEnd();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        /// 在标准情况下(自定义行为树的情况除外), 该事件在节点被添加到行为树的时候触发
        protected virtual void OnAwake() { }

        /// 在标准情况下(自定义行为树的情况除外), 该事件应该跟随其所在的组件一同开启与关闭
        protected virtual void OnEnable() { }

        /// 与 OnEnable 同理
        protected virtual void OnDisable() { }

        /// 在标准情况下, 不会主动调用该事件, 应由外部自行调用, 代表该行为树开始Tick
        protected virtual void OnReset() { }

        /// 在标准情况下, 内部不会主动调用该事件, 应由外部自行调用, 代表该行为树终止Tick
        protected virtual void OnAbort() { }

        /// 在标准情况下, 当所在组件被销毁时触发
        protected virtual void OnDestroy() { }

        // --- 下面的事件的名字都包含Node, 所以他们不像上面的事件都是全局事件, 同一个行为树下, 所有的Node都会同一时间触发事件, 下面这些事件每个Node触发的时间都不同.

        // 当节点评估, 返回的结果将决定还是否需要进入该节点
        // 如果评估为假, 则该节点会被跳过, 状态会被认为是 continue, 即, 该节点被忽略了
        protected abstract bool OnNodeEvaluate();

        /// 当节点执行
        protected abstract NodeStatus OnNodeTick();

        /// 当节点刚进入Running状态时触发, 如果节点没有进入Running状态, 那么即使被Tick也不会触发
        protected virtual void OnNodeRunningStart() { }

        /// 当节点为Running状态时持续触发
        protected virtual void OnNodeRunning() { }

        /// 当节点刚离开Running状态时触发
        protected virtual void OnNodeRunningEnd() { }

        public Enumerable ForeachChildren() => new Enumerable(this);

        /// <summary>
        /// 特别注意, 这种方法只能遍历一层子节点, 无法遍历子节点的子节点, 如果想的话, 需要自己用递归方式实现.
        /// </summary>
        /// <returns></returns>
        public abstract IteratorList<INode> ForeachChildrenExcludeGrandchild();

        /// <summary>
        /// 这种方式可以通过传入数据参数的方式来避免产生GC, 同时也能够遍历子节点的子节点, 缺点就是需要注意, 如果不小心使用了外部的变量, 那么依然会产生GC
        /// </summary>
        public abstract void ForeachChildren<TNode>(Action<INode, TNode> action, TNode userData = default);

        public TNode GetNodeInParent<TNode>() {
            var parent = this.Parent;
            while (parent != null) {
                if (parent is TNode t) {
                    return t;
                }

                parent = parent.Parent;
            }

            return default;
        }

        public abstract TNode GetNodeInChildren<TNode>();

        public abstract TNode[] GetNodesInChildren<TNode>();

        public abstract void GetNodesInChildren<TNode>(List<TNode> cache);

        public override string ToString() {
            return string.IsNullOrEmpty(this._name) ? this.GetType().Name : this._name;
        }

        public struct Enumerable : IEnumerable {
            private readonly INode _node;

            public Enumerable(INode node) {
                this._node = node;
            }

            public Enumerator GetEnumerator() => new Enumerator(this._node);
            IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this._node);
        }

        public struct Enumerator : IEnumerator<INode> {
            private Queue<INode> _queue;

            public INode Current { get; private set; }

            object IEnumerator.Current => this.Current;

            public Enumerator(INode root) {
                this._queue = ObjectPool.Rent<Queue<INode>>();
                this.Current = root;

                foreach (var child in root.ForeachChildrenExcludeGrandchild()) {
                    this._queue.Enqueue(child);
                }
            }

            public bool MoveNext() {
                while (this._queue.Count > 0) {
                    var node = this._queue.Dequeue();
                    this.Current = node;
                    foreach (var child in node.ForeachChildrenExcludeGrandchild()) {
                        this._queue.Enqueue(child);
                    }

                    return true;
                }

                return false;
            }

            public void Reset() { }

            public void Dispose() {
                this._queue.Clear();
                ObjectPool.Return(this._queue);
                this._queue = null;
            }
        }
    }
}